﻿using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Internal;
using OpenCvSharp.Dnn;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Polyline = Autodesk.AutoCAD.DatabaseServices.Polyline;

namespace WCAD
{
    public static class EmEntityMethod
    {
        public static void AddEntities<T>(this List<T> ents, int n = 0) where T : Entity
        {
            Database db = HostApplicationServices.WorkingDatabase;
            using (Transaction tran = db.TransactionManager.StartTransaction())
            {
                BlockTable bt = (BlockTable)db.BlockTableId.GetObject(OpenMode.ForRead);
                BlockTableRecord btr = (BlockTableRecord)bt[n == 0 ? BlockTableRecord.ModelSpace : BlockTableRecord.PaperSpace].GetObject(OpenMode.ForWrite);
                foreach (var item in ents)
                {
                    if (item.IsNewObject)
                    {
                        var id = btr.AppendEntity(item);
                        tran.AddNewlyCreatedDBObject(item, true);
                    }
                    else
                    {
                        ;
                    }
                }
                tran.Commit();
            }
        }
        public static void AddEntity(this Entity ent, int n = 0)
        {
            AddEntities(new List<Entity>() { ent }, n);
        }
        public static List<Point3d> IntersectWith(this Entity ent1, Entity ent2, Intersect intersect = Intersect.OnBothOperands)
        {
            Point3dCollection pos = new Point3dCollection();
            ent1.IntersectWith(ent2, intersect, pos, IntPtr.Zero, IntPtr.Zero);
            List<Point3d> points = new List<Point3d>();
            foreach (Point3d item in pos)
            {
                points.Add(item);
            }
            return points;
        }
        public static Extents3d GetExtents3d<T>(this List<T> ents, bool dyn = false, bool xc = false) where T : Entity
        {
            ents = ents.FindAll(x => x.Bounds != null);
            bool b = true;
            Extents3d extents = new Extents3d(new Point3d(), new Point3d());
            for (int i = 0; i < ents.Count; i++)
            {
                if (xc)
                {
                    if (ents[i] is BlockReference bl)
                    {
                        if (BmBlcokMethod.IsXCBlcok(bl))
                        {
                            Extents3d exten = BmBlcokMethod.TryGetSpatialFilter(bl).GetQueryBounds();
                            if (b)
                            {
                                extents = exten;
                                b = false;
                            }
                            else
                            {
                                extents.AddExtents(exten);
                            }
                            continue;
                        }
                    }
                }
                if (dyn)
                {
                    if (ents[i] is BlockReference bl)
                    {
                        if (BmBlcokMethod.IsDynBlock(bl))
                        {
                            List<Entity> entities = BmBlcokMethod.ExplodeBlockReference(bl);
                            entities = entities.FindAll(x => x.Visible == true && x.Bounds != null);
                            if (b)
                            {
                                extents = entities[0].GeometricExtents;
                                b = false;
                            }
                            entities.ForEach(x => extents.AddExtents(x.GeometricExtents));
                            continue;
                        }
                    }
                }
                if (b)
                {
                    extents = ents[i].GeometricExtents;
                    b = false;
                }
                else
                {
                    extents.AddExtents(ents[i].GeometricExtents);
                }
            }
            return extents;
        }
        public static Extents3d GetExtents3d(this Entity ent, bool dyn = false, bool xc = false)
        {
            return GetExtents3d(new List<Entity>() { ent }, dyn,xc);
        }
        public static Point3d CenterPoint<T>(this List<T> ents) where T : Entity
        {
            double minx = ents.Min(x => x.GeometricExtents.MinPoint.X);
            double miny = ents.Min(x => x.GeometricExtents.MinPoint.Y);
            double maxx = ents.Max(x => x.GeometricExtents.MaxPoint.X);
            double maxy = ents.Max(x => x.GeometricExtents.MaxPoint.Y);
            return new Point3d(minx / 2 + maxx / 2, miny / 2 + maxy / 2, 0);
        }

        public static Point3d CenterPoint(this Entity ent)
        {
            return CenterPoint(new List<Entity>() { ent });
        }

        public static double GetWidth<T>(this List<T> ents) where T : Entity
        {
            double minx = ents.Min(x => x.GeometricExtents.MinPoint.X);
            double maxx = ents.Max(x => x.GeometricExtents.MaxPoint.X);
            return maxx-minx;
        }
        public static double GetWidth<T>(this T ent) where T : Entity
        {
            return GetWidth<T>(new List<T>() { ent });
        }
        public static double GetHeight<T>(this List<T> ents) where T : Entity
        {
            double miny = ents.Min(x => x.GeometricExtents.MinPoint.Y);
            double maxy = ents.Max(x => x.GeometricExtents.MaxPoint.Y);
            return maxy - miny;
        }
        public static double GetHeight<T>(this T ent) where T : Entity
        {
            return GetHeight<T>(new List<T>() { ent });
        }
        public static void EraseEntities<T>(this List<T> ents) where T : Entity
        {
            Database db = HostApplicationServices.WorkingDatabase;
            using (Transaction tran = db.TransactionManager.StartTransaction())
            {
                foreach (var item in ents)
                {
                    item.ObjectId.GetObject(OpenMode.ForWrite);
                    if (item.IsErased) continue;
                    if (item.ObjectId == ObjectId.Null) continue;
                    item.Erase();
                }
                tran.Commit();
            }
        }
        public static void EraseEntity<T>(this T ent) where T : Entity
        {
            EraseEntities(new List<T>() { ent });
        }
        public static List<Entity> ExplodeEntity<T>(T ent) where T : Entity
        {
            DBObjectCollection dbs = new DBObjectCollection();
            ent.Explode(dbs);
            List<Entity> entities = new List<Entity>();
            foreach (Entity item in dbs)
            {
                entities.Add(item);
            }
            return entities;
        }
        public static void EditEntities<T>(this List<T> entity, Action action) where T : Entity
        {
            Database db = HostApplicationServices.WorkingDatabase;
            using (Transaction tran = db.TransactionManager.StartTransaction())
            {
                entity.ForEach(e => e.ObjectId.GetObject(OpenMode.ForWrite));
                action();
                tran.Commit();
            }
        }
        public static void EditEntity(this Entity entity, Action action)
        {
            EditEntities(new List<Entity>() { entity }, action);
        }
        public static bool IsOverlapping(Entity entity1, Entity entity2)
        {
            Point3dCollection pos = new Point3dCollection();
            entity1.IntersectWith(entity2, Intersect.OnBothOperands, pos, IntPtr.Zero, IntPtr.Zero);
            return pos.Count > 0;
        }
        public static bool IsSameColor(Entity entity1, Entity entity2)
        {
            bool bb = entity1.Color == entity2.Color;
            return bb;
        }
        public static void Move(this Entity entity, Point3d point1, Point3d point2)
        {
            entity.Move(point2 - point1);
        }
        public static void Rotation(this Entity entity, Point3d point, double angle, bool clockwise = false)
        {
            Matrix3d matrix = Matrix3d.Rotation(angle, clockwise ? -Vector3d.ZAxis : Vector3d.ZAxis, point);
            if (entity.IsNewObject)
            {
                entity.TransformBy(matrix);
            }
            else
            {
                Database database = HostApplicationServices.WorkingDatabase;
                using (Transaction tran = database.TransactionManager.StartTransaction())
                {
                    entity.ObjectId.GetObject(OpenMode.ForWrite);
                    entity.TransformBy(matrix);
                    tran.Commit();
                }
            }
        }
        public static void Move(this Entity entity, double x = 0, double y = 0, double z = 0)
        {
            entity.Move(new Vector3d(x, y, z));
        }
        public static void Move(this Entity entity, Vector3d vector)
        {
            Matrix3d matrix = Matrix3d.Displacement(vector);
            if (entity.IsNewObject)
            {
                entity.TransformBy(matrix);
            }
            else
            {
                Database database = HostApplicationServices.WorkingDatabase;
                using (Transaction tran = database.TransactionManager.StartTransaction())
                {
                    entity.ObjectId.GetObject(OpenMode.ForWrite);
                    entity.TransformBy(matrix);
                    tran.Commit();
                }
            }
        }

        public static List<List<T>> GroupEntities<T>(List<T> entities, Func<T, T, bool> func) where T : Entity
        {
            List<List<T>> result = new List<List<T>>();
            List<Friend> friends = new List<Friend>();
            for (int i = 0; i < entities.Count; i++)
            {
                Friend friend = new Friend(i);
                friends.Add(friend);
            }
            for (int i = 0; i < entities.Count - 1; i++)
            {
                for (int j = i + 1; j < entities.Count; j++)
                {

                    if (func(entities[i], entities[j]))
                    {
                        friends[i].Friends.Add(friends[j]);
                        friends[j].Friends.Add(friends[i]);
                    }
                }
            }

            while (friends.Count > 0)//整个朋友池还没清空的时候，一直循环，表示还有新的组没有被分
            {
                List<T> list = new List<T>();//建一个组
                Queue<Friend> queue = new Queue<Friend>();//新建一个队列
                queue.Enqueue(friends[0]);//队列尾部加入friends的第一个
                friends.RemoveAt(0);//friends移除第一个
                while (queue.Count > 0)//当队列数量大于0时。一直循环，因为代表这一个组的成员还没有找全
                {
                    Friend friend = queue.Dequeue();//从队列头部弹出一个friend
                    list.Add(entities[friend.Id]);//把这个人对应的T加到分组中
                    foreach (Friend f in friend.Friends)//遍历他所有的朋友
                    {
                        if (friends.Contains(f))//如果朋友池中还有他的朋友，说明之前还没有被别人选择过
                        {
                            queue.Enqueue(f);//把这个朋友加入队列尾部
                            friends.Remove(f);//把这个朋友从朋友池中剔除，不让他被别的人选择
                        }
                    }
                }
                result.Add(list);//整个队列清空之后，说明这一组人员已经到齐， 把这一组添加到结果里
            }

            return result;
        }

        public static List<List<Entity>> GroupEntities(List<Entity> entities)
        {
            List<List<Entity>> result = new List<List<Entity>>();
            List<Friend> friends = new List<Friend>();
            for (int i = 0; i < entities.Count; i++)
            {
                Friend friend = new Friend(i);
                friends.Add(friend);
            }
            for (int i = 0; i < entities.Count - 1; i++)
            {
                for (int j = i + 1; j < entities.Count; j++)
                {

                    if (IsOverlapping(entities[i], entities[j]))
                    {
                        friends[i].Friends.Add(friends[j]);
                        friends[j].Friends.Add(friends[i]);
                    }
                }
            }

            while (friends.Count > 0)//整个朋友池还没清空的时候，一直循环，表示还有新的组没有被分
            {
                List<Entity> list = new List<Entity>();//建一个组
                Queue<Friend> queue = new Queue<Friend>();//新建一个队列
                queue.Enqueue(friends[0]);//队列尾部加入friends的第一个
                friends.RemoveAt(0);//friends移除第一个
                while (queue.Count > 0)//当队列数量大于0时。一直循环，因为代表这一个组的成员还没有找全
                {
                    Friend friend = queue.Dequeue();//从队列头部弹出一个friend
                    list.Add(entities[friend.Id]);//把这个人对应的Entity加到分组中
                    foreach (Friend f in friend.Friends)//遍历他所有的朋友
                    {
                        if (friends.Contains(f))//如果朋友池中还有他的朋友，说明之前还没有被别人选择过
                        {
                            queue.Enqueue(f);//把这个朋友加入队列尾部
                            friends.Remove(f);//把这个朋友从朋友池中剔除，不让他被别的人选择
                        }
                    }
                }
                result.Add(list);//整个队列清空之后，说明这一组人员已经到齐， 把这一组添加到结果里
            }

            return result;
        }
        class Friend
        {
            public int Id { get; set; }//序列号
            public List<Friend> Friends { get; set; }//朋友们
            public Friend(int id)
            {
                Id = id;
                Friends = new List<Friend>();
            }
        }
        public static Point3d GetClosestPoint(List<Entity> ents, Point3d point, Vector3d vector)
        {
            Ray ray = new Ray();
            ray.BasePoint = point;
            ray.UnitDir = vector;
            List<Curve> curves = new List<Curve>();
            foreach (var ent in ents)
            {
                if(ent is Curve)
                {
                    curves.Add((Curve)ent);
                }
                else if(ent is BlockReference bl)
                {
                    if (ray.IntersectWith(ent).Count == 0) continue;
                    List<Entity> entities=BmBlcokMethod.ExplodeBlockReference(bl,true);
                    foreach (var item in entities)
                    {
                        if(item is Curve)
                        {
                            curves.Add((Curve)item);
                        }
                    }
                }
            }
            Point3d result = point;
            double dis = double.MaxValue;
            foreach (var cur in curves)
            {
                List<Point3d> pos = ray.IntersectWith(cur);
                if (pos.Count == 0) continue;
                foreach (var item in pos)
                {
                    if (point.DistanceTo(item) < dis)
                    {
                        result = item;
                        dis = point.DistanceTo(item);
                    }
                }
            }
            return result;
        }
    }
}
